#include <seminix/syscall.h>
#include <seminix/tcb.h>
#include <seminix/smp.h>
#include <cap/ipc_buffer.h>
#include <cap/cap.h>
#include <cap/thread.h>
#include <cap/cnode.h>

SYSCALL_DEFINE1(set_tid_address, int __user *, tidptr)
{
    current->tid_address = (unsigned long *)tidptr;

    return task_tid_nr(current);
}

static void _do_copy_thread(void *info)
{
    struct copy_thread_param *p = (struct copy_thread_param *)info;

    copy_thread(p);
    wake_up_new_task(p->new_tsk);
}

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
		 int __user *, parent_tidptr,
		 int __user *, child_tidptr,
		 unsigned long, tls)
{
    int new_tcb = get_current_mr(0);
    unsigned long stack = newsp;
    int ret;
    unsigned long flags = TCB_WAKEUP_PARENT_GETTID | TCB_WAKEUP_CHILD_SETTID
        | TCB_WAKEUP_SETTLS;
    cap_t *new_cap;
    struct copy_thread_param param = { 0 };

    new_cap = cnode_capget(new_tcb, cap_thread_cap);
    if (IS_ERR(new_cap))
        return PTR_ERR(new_cap);

    param.new_tsk = CAP_THREAD_PTR(new_cap)->task;
    param.copy_tsk = current;
    param.pc = 0;
    param.reg0 = 0;
    param.stack = stack;
    param.flags = flags;
    if (flags & TCB_WAKEUP_PARENT_GETTID) {
        param.parent_tidptr = (unsigned long *)parent_tidptr;
        if (put_user(task_tid_nr(param.new_tsk), param.parent_tidptr))
            return -EFAULT;
    }
    if (flags & (TCB_WAKEUP_CHILD_SETTID | TCB_WAKEUP_SETTLS)) {
        param.child_tidptr = (unsigned long *)child_tidptr;
    }

    param.new_tsk->tid_address = (flags & TCB_WAKEUP_CHILD_SETTID) ? param.child_tidptr : NULL;

    ret = smp_call_function_single(task_cpu(param.copy_tsk), _do_copy_thread, &param, false);
    if (ret)
        ret = -SERRNO_EINVAL;
    cnode_capput(new_cap);
    return param.new_tsk->tid;
}
